package miniox

import (
	"bytes"
	"common/configx"
	"common/zapx"
	"context"
	"fmt"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
	"go.uber.org/zap"
	"sync"
)

var _Once sync.Once

type MinioX struct {
	*minio.Client
	Config     *configx.MinioConfig
	Ctx        context.Context
	bucketInfo *BucketInfo
}

type BucketInfo struct {
	Exists bool
}

type Opt func(mn *MinioX)

var instance *MinioX

// WithEndpointOpt 断点设置
func WithEndpointOpt(endpoint string) Opt {
	return func(mn *MinioX) {
		mn.Config.Endpoint = endpoint
	}
}

// WithAccessKeyId access key
func WithAccessKeyId(accessKeyId string) Opt {
	return func(mn *MinioX) {
		mn.Config.AccessKeyID = accessKeyId
	}
}

// WithSecretAccessKey secret access key
func WithSecretAccessKey(secretAccessKey string) Opt {
	return func(mn *MinioX) {
		mn.Config.SecretAccessKey = secretAccessKey
	}
}

// WithUseSSL 是否使用安全连接
func WithUseSSL(ssl bool) Opt {
	return func(mn *MinioX) {
		mn.Config.UseSSL = ssl
	}
}

// WithMinioConfig minio配置
func WithMinioConfig(config *configx.MinioConfig) Opt {
	return func(mn *MinioX) {
		mn.Config = config
	}
}

// Instance 单例
func Instance(opts ...Opt) *MinioX {
	_Once.Do(func() {
		instance = &MinioX{
			nil,
			&configx.MinioConfig{},
			context.Background(),
			&BucketInfo{},
		}
		for _, opt := range opts {
			opt(instance)
		}
		if err := instance.Connect(); err != nil {
			zapx.Logger.Error("初始化minio失败", zap.Error(err))
			panic(err)
		}
	})
	return instance
}

// Connect 连接minio
func (mn *MinioX) Connect() error {
	c, err := minio.New(mn.Config.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(mn.Config.AccessKeyID, mn.Config.SecretAccessKey, ""),
		Secure: mn.Config.UseSSL,
	})
	if err != nil {
		return err
	}
	mn.Client = c
	return nil
}

// SetContext 设置新的context
func (mn *MinioX) SetContext(ctx context.Context) {
	mn.Ctx = ctx
}

// PutBytes 上传文件(字节上传)
func (mn *MinioX) PutBytes(objectName string, bs []byte, opts minio.PutObjectOptions) (info minio.UploadInfo, err error) {
	if berr := mn.CheckAndAutoBucket(); berr != nil {
		return minio.UploadInfo{}, berr
	}
	return mn.PutObject(mn.Ctx, mn.Config.BucketName, objectName, bytes.NewReader(bs), int64(len(bs)), opts)
}

// CheckAndAutoBucket 检查bucket是否存在,不存在自动创建
func (mn *MinioX) CheckAndAutoBucket() error {
	if mn.bucketInfo.Exists {
		return nil
	}
	//先判断这个bucket是否存在
	var err error
	mn.bucketInfo.Exists, err = mn.BucketExists(mn.Ctx, mn.Config.BucketName)
	if err == nil {
		if !mn.bucketInfo.Exists {
			//创建bucket
			err = mn.MakeBucket(mn.Ctx, mn.Config.BucketName, minio.MakeBucketOptions{})
		}
	}
	if err == nil {
		mn.bucketInfo.Exists = true
	}
	return err
}

// ParseUri 拼接url
func (mn *MinioX) ParseUri(objectName string) string {
	var prefix string = "http"
	if mn.Config.UseSSL {
		prefix = "https"
	}
	return fmt.Sprintf("%s://%s/%s/%s", prefix, mn.Config.Endpoint, mn.Config.BucketName, objectName)
}
